%  This file is part of Erlywog.

%  Erlywog is free software: you can redistribute it and/or modify
%  it under the terms of the GNU General Public License as published by
%  the Free Software Foundation, either version 3 of the License, or
%  (at your option) any later version.

%  Erlywog is distributed in the hope that it will be useful,
%  but WITHOUT ANY WARRANTY; without even the implied warranty of
%  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
%  GNU General Public License for more details.

%  You should have received a copy of the GNU General Public License
%  along with Erlywog.  If not, see <http://www.gnu.org/licenses/>.

%  Copyright Jasm Sison 2009

-module(md5).
-export([
	hex/1
	,hex0/1
	,hex1/1
	,hex2/1
	,hex3/1
	,hex4/1
	,hex5/1
	,make_string/1
	,md51/1
	,md52/1
	,md54/1
	,md55/1
	]).
-include_lib("eunit/include/eunit.hrl").

hex(Str) ->
  hex5(Str).

%% broken
hex0(S) -> [ hexify(N) || <<N>> <= erlang:md5(S) ].

hexify(N) when N < 10 -> $0 + N;
hexify(N) when N >= 10, N < 16 -> $a + hexify(N-10).

%% working
hex1(S) ->
  list_to_binary(lists:flatten([io_lib:format("~.16b",[N]) || <<N>> <= erlang:md5(S)])).
  
hexlist1(S) ->
  lists:flatten([io_lib:format("~.16b",[N]) || <<N>> <= erlang:md5(S)]).

%% working
hex2(S) ->
  list_to_binary(
  lists:flatten(
  io_lib:format(lists:flatten(
      array:to_list(
        array:new([{size,16}, {default, "~.16b"}])))
    , binary_to_list(erlang:md5(S))))).
    
hexlist2(S) ->
  lists:flatten(
  io_lib:format(lists:flatten(
      array:to_list(
        array:new([{size,16}, {default, "~.16b"}])))
    , binary_to_list(erlang:md5(S)))).


%% broken
hex3(S) ->
 list_to_binary([ HexChar || <<Nibble>> <= erlang:md5(S), [HexChar] <- io_lib:format("~.16b", [Nibble]) ]).

%% working...
hex4(S) ->
  list_to_binary(lists:flatten(lists:map(fun(V) -> 
    string:to_lower(httpd_util:integer_to_hexlist(V)) end, 
    binary_to_list(erlang:md5(S))))).
    
hexlist4(S) ->
  lists:flatten(lists:map(fun(V) -> 
    string:to_lower(httpd_util:integer_to_hexlist(V)) end, 
    binary_to_list(erlang:md5(S)))).
    
%% another try, maybe this time the zeroes will remain
hex5(B) ->
  list_to_binary(hexlist5(binary_to_list(erlang:md5(B)), [])).
    
hexlist5(B) ->
  hexlist5(binary_to_list(erlang:md5(B)), []).

hexlist5([], Acc) ->
    lists:reverse(Acc);
hexlist5([N|Rest], Acc) ->
    HighNibble = (N band 16#f0) bsr 4,
    LowNibble = (N band 16#0f),
    hexlist5(Rest, [hexdigit(LowNibble), hexdigit(HighNibble)|Acc]).

hexdigit(0) -> $0;
hexdigit(1) -> $1;
hexdigit(2) -> $2;
hexdigit(3) -> $3;
hexdigit(4) -> $4;
hexdigit(5) -> $5;
hexdigit(6) -> $6;
hexdigit(7) -> $7;
hexdigit(8) -> $8;
hexdigit(9) -> $9;
hexdigit(10) -> $a;
hexdigit(11) -> $b;
hexdigit(12) -> $c;
hexdigit(13) -> $d;
hexdigit(14) -> $e;
hexdigit(15) -> $f.

%% seperate function tests
hex5_test_() ->
  ?assert( list_to_binary(hexlist5("abcd")) =:= hex5("abcd")),
  ?assert( hexlist5("abcd") =:= binary_to_list(hex5("abcd"))).

%% comparison tests!
make_string(N) ->
  base64:encode(lists:map(fun (_) -> random:uniform(97)+$\s+1 end, lists:seq(1,N))).
  
make_string_list() ->
  [ make_string(Nr) || Nr <- lists:seq(1000,1500)].
  
comparison_test_() ->
  %% 1,2,5 comparison test
  %% prepare input
  StringList = make_string_list()
  , ?assert(lists:foldl(fun(Truth, Accum) -> Truth =:= Accum end, true, lists:flatten([
  %% check correctness of 1,2
  [ hex1(Str) =:= hex2(Str) || Str <- StringList ]
  %% check correctness of 2,5
  , [ hex2(Str) =:= hex5(Str) || Str <- StringList ]
  %% check correctness of 1,5
  , [ hex1(Str) =:= hex5(Str) || Str <- StringList ]
  %% check correctness of 2,4
  , [ hex2(Str) =:= hex4(Str) || Str <- StringList ]
  %% check correctness of 4,5
  , [ hex4(Str) =:= hex5(Str) || Str <- StringList ]
  %% check correctness of 4,1
  , [ hex1(Str) =:= hex4(Str) || Str <- StringList ]
  %% check string sizes
  , lists:map(fun(Bin) -> length(binary_to_list(Bin)) =:= 32 end
  , lists:flatten([ [hex1(Str), hex2(Str), hex4(Str), hex5(Str)] || Str <- StringList ]))
  ]))).

%% profiling tests!
md51(Str) ->
  hexlist1(Str).
md52(Str) ->
  hexlist2(Str).
md54(Str) ->
  hexlist4(Str).
md55(Str) ->
  hexlist5(Str).

time_test_() ->
  StringList = make_string_list()
  , T = timer
  , [
      {1, lists:foldl(fun(Add, Accum) -> Add + Accum end, 0, [ Time || {Time, _} <- [ T:tc(md5,md51,[Str]) || Str <- StringList ]])}
    , {2, lists:foldl(fun(Add, Accum) -> Add + Accum end, 0, [ Time || {Time, _} <- [ T:tc(md5,md52,[Str]) || Str <- StringList ]])}
    , {4, lists:foldl(fun(Add, Accum) -> Add + Accum end, 0, [ Time || {Time, _} <- [ T:tc(md5,md54,[Str]) || Str <- StringList ]])}
    , {5, lists:foldl(fun(Add, Accum) -> Add + Accum end, 0, [ Time || {Time, _} <- [ T:tc(md5,md55,[Str]) || Str <- StringList ]])}
  ].
  
cumulative_time_test_() ->
  Fois = lists:seq(0,50)
  , Runtimes = lists:flatten([ time_test_() || _X <- Fois ])
  , [
        {1, lists:foldl(fun(Add, Accum) -> Add + Accum end, 0, [X || {1,X} <- Runtimes]) }
      , {2, lists:foldl(fun(Add, Accum) -> Add + Accum end, 0, [X || {2,X} <- Runtimes]) }
      , {4, lists:foldl(fun(Add, Accum) -> Add + Accum end, 0, [X || {4,X} <- Runtimes]) }
      , {5, lists:foldl(fun(Add, Accum) -> Add + Accum end, 0, [X || {5,X} <- Runtimes]) }
  ].